package lpc

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/exec"
	"path/filepath"
	"sort"
	"strings"
	"time"

	D "gitee.com/dark.H/go-remote-repl/datas"
	"github.com/google/uuid"
)

// select app ,
// set option
// create session / run
//
type Context map[string]string

func SessionLs() (o []string) {
	old, ok := D.MemDB.Kd["sessions"]
	if ok {
		o = old.Keys()
	}
	sort.Strings(o)
	return
}

func SessionLsName() (o []string) {
	old, ok := D.MemDB.Kd["sessions"]
	if ok {
		s := old.Keys()
		for _, k := range s {
			sess := SessionQuery(k)
			q := strings.Fields(sess.Args)
			if len(q) > 0 {
				v := GetAPINamesR(q[0])
				o = append(o, k+" : "+v+" "+sess.State)
			}
		}
		sort.Strings(o)
	}
	return
}

func SessionUpdate(sess *Session) {
	old, ok := D.MemDB.Kd["sessions"]
	D.Locker.Lock()
	defer D.Locker.Unlock()
	if !ok {
		buf, _ := json.MarshalIndent(sess, "", "\t")
		old = D.Gi{
			sess.Id: string(buf),
		}
	} else {
		buf, _ := json.MarshalIndent(sess, "", "\t")
		if o := SessionQuery(sess.Id); o != nil {
			if o.State != "Finished" {
				o.KillMySelf()
				// o.Remove()
			}
		}
		old[sess.Id] = string(buf)
	}
	D.MemDB.Kd["sessions"] = old
}
func SessionDel(id string) {
	old, ok := D.MemDB.Kd["sessions"]
	if ok {
		D.Locker.Lock()
		defer D.Locker.Unlock()
		delete(old, id)
	}
}

func SessionQuery(sessid string) (sess *Session) {
	old, ok := D.MemDB.Kd["sessions"]
	if ok {
		if raw, ok := old[sessid]; ok {
			sess = new(Session)
			if err := json.Unmarshal([]byte(raw.(string)), sess); err != nil {
				log.Println("session query err:", err)
			} else {
				return
			}
		}
	}
	return
}

type Session struct {
	Id        string `json:"id"`
	OutputLog string `json:"output"`
	State     string `json:"state"`
	Args      string `json: "args"`
	PID       int    `json:"pid"`
}

func NewSession(name string, cmd string) *Session {
	U := uuid.NewMD5(uuid.UUID{}, []byte(name))
	logFile := filepath.Join(os.TempDir(), U.String()+".log")

	return &Session{
		Id:        U.String(),
		OutputLog: logFile,
		State:     "init",
		Args:      cmd,
	}
}

func (session *Session) Run(afterDo func(outlog string), errDo func(err error)) {
	args := strings.Fields(session.Args)
	cmd := exec.Command(args[0], args[1:]...)
	log.Println("as run:", strings.Join(args, ","))
	cmd.Env = os.Environ()
	// open the out file for writing
	if _, err := os.Stat(session.OutputLog); err == nil {
		os.Remove(session.OutputLog)
	}
	outfile, err := os.OpenFile(session.OutputLog, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)
	if err != nil {
		session.State = err.Error()
		SessionUpdate(session)
		os.Remove(session.OutputLog)
		errDo(err)
		return
	}
	defer outfile.Close()
	cmd.Stdin = os.Stdin
	cmd.Stdout = outfile
	cmd.Stderr = outfile
	err = cmd.Start()
	if err != nil {
		session.State = err.Error()
		SessionUpdate(session)
		os.Remove(session.OutputLog)
		errDo(err)
		return
	}
	session.PID = cmd.Process.Pid
	session.State = "starting"
	SessionUpdate(session)
	cmd.Wait()
	session.State = "Finished"
	SessionUpdate(session)
	if afterDo != nil {
		afterDo(session.OutputLog)
	}
}

func (sess *Session) KillMySelf() ([]byte, error) {
	if sess.PID != 0 {
		pid := sess.PID
		if GOOS == "windows" {
			cmd := exec.Command("cmd.exe", "/c", fmt.Sprintf("TASKKILL /PID %d", pid))
			return cmd.Output()
		} else {
			cmd := exec.Command("bash", "-c", fmt.Sprintf("kill -9 %d", pid))
			return cmd.Output()
		}
	}
	sess.State = "Finished"
	return []byte("no pid"), nil
}

func (sess *Session) Remove() {
	if _, err := os.Stat(sess.OutputLog); err == nil {
		sess.KillMySelf()
		os.Remove(sess.OutputLog)
	}
	SessionDel(sess.Id)
}

func (sess *Session) OutputPipe(conn net.Conn) {
	fp, err := os.Open(sess.OutputLog)
	if err != nil {
		conn.Write([]byte(err.Error()))
		conn.Write([]byte(sess.State))
		conn.Close()
	}
	finfo, _ := fp.Stat()
	defer fp.Close()
	reader := bufio.NewReader(fp)
	for {
		line, err := reader.ReadString('\n')
		switch err {
		case io.EOF:
			time.Sleep(1 * time.Second)
			if cureInfo, err := fp.Stat(); err != nil {
				break
			} else {
				if !os.SameFile(finfo, cureInfo) {
					break
				}
			}
		case nil:
			conn.Write([]byte(line))
		default:
			break
		}
	}
	// datas.Copy(conn, fp)
}
